/*
 * Copyright (C) 2014 luke.
 * 
 * This file is part of Silvie.
 * 
 * Silvie is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Silvie is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Silvie.  If not, see <http://www.gnu.org/licenses/>.
 */

package silvie.resources.chrmodel;

import java.awt.Point;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.ArrayList;
import java.util.List;

import javax.media.j3d.Appearance;
import javax.media.j3d.GeometryArray;
import javax.media.j3d.Shape3D;
import javax.vecmath.Point3f;
import javax.vecmath.TexCoord2f;

import silvie.gui.Preview;
import silvie.gui.previewers.AbstractPreviewer;
import silvie.gui.previewers.Previewer3D;
import silvie.resources.AbstractResource;
import silvie.resources.chrmodel.chunks.AbstractChunk;
import silvie.resources.chrmodel.chunks.Chunk0x7f01;
import silvie.resources.chrmodel.chunks.Chunk0x7f02;
import silvie.resources.chrmodel.chunks.Chunk0x7f04;
import silvie.resources.chrmodel.chunks.Chunk0x7f05;
import silvie.resources.chrmodel.chunks.Chunk0x7f06;
import silvie.resources.chrmodel.chunks.Chunk0x8000;
import silvie.resources.chrmodel.structures.Polygon;
import utils.DataReader;

import com.sun.j3d.utils.geometry.GeometryInfo;
import com.sun.j3d.utils.geometry.NormalGenerator;
import com.sun.j3d.utils.geometry.Stripifier;
import com.sun.j3d.utils.image.TextureLoader;

/**
 * AbstractResource specialization for Silver 3D models (CHR files).
 * 
 * @author luke
 */
public class CHRModel extends AbstractResource {
	private static final int SHAPE_POSITION_IDS_CHUNK_ID = 0x7f06;
	private static final int SHAPE_POSITIONS_CHUNK_ID = 0x7f05;
	private static final int TEXTURE_CHUNK_ID = 0x7f04;
	private static final int SHAPE_CHUNK_ID = 0x7f02;
	private static final int TEXTURE_OFFSETS_CHUNK_ID = 0x7f01;
	private static final int ROOT_CHUNK_ID = 0x8000;
	private Shape3D shape3d;

	/**
	 * CHRModel constructor.
	 * 
	 * @param CHRFile the CHR file.
	 */
	public CHRModel(File CHRFile) {
		super(CHRFile, false);
	}

	@Override
	public void extract() throws Exception {
		/*
		 * Some models don't work at all (LIFT.CHR and ITEMS.CHR). They seem to
		 * be incomplete/garbage files that were probably included by mistake
		 */
		super.extract();
		int rootChunkDataSize = super.resourceBytes.length - 2 * DataReader.INT_SIZE;
		byte[] rootChunkData = new byte[rootChunkDataSize];
		System.arraycopy(super.resourceBytes, 2 * DataReader.INT_SIZE, rootChunkData, 0, rootChunkDataSize);
		AbstractChunk rootChunk = new Chunk0x8000(ROOT_CHUNK_ID, rootChunkData);
		rootChunk.readContents();
		Chunk0x7f01 textureOffsetsChunk = (Chunk0x7f01) rootChunk.getChunksById(TEXTURE_OFFSETS_CHUNK_ID).get(0);
		List<AbstractChunk> shapeChunks = rootChunk.getChunksById(SHAPE_CHUNK_ID);
		Chunk0x7f04 textureChunk = (Chunk0x7f04) rootChunk.getChunksById(TEXTURE_CHUNK_ID).get(0);
		Chunk0x7f05 shapePositionsChunk = (Chunk0x7f05) rootChunk.getChunksById(SHAPE_POSITIONS_CHUNK_ID).get(0);
		Chunk0x7f06 shapePositionIdsChunk = (Chunk0x7f06) rootChunk.getChunksById(SHAPE_POSITION_IDS_CHUNK_ID).get(0);
		shapePositionIdsChunk.mapPositions(shapePositionsChunk);
		List<Point3f> verticesList = new ArrayList<Point3f>();
		List<Integer> stripCountsList = new ArrayList<Integer>();
		List<TexCoord2f> texCoordsList = new ArrayList<TexCoord2f>();
		int[] textureOffsets = textureOffsetsChunk.getTextureOffsets();
		short[] texturePixelIndices = textureChunk.getTexturePixelIndices();
		int textureWidth = textureChunk.getWidth();
		int textureHeight = textureChunk.getHeight();
		for (AbstractChunk chunk : shapeChunks) {
			Chunk0x7f02 shape = ((Chunk0x7f02) chunk);
			int shapeId = shape.getShapeId();
			List<Polygon> polygons = shape.getPolygons();
			List<Point3f> shapeVertices = shape.getVertices();
			for (Polygon polygon : polygons) {
				int textureOffsetIndex = polygon.getTextureOffsetIndex();
				int textureStartPixel = textureOffsets[textureOffsetIndex];
				polygon.setPixelSearchStart(textureStartPixel);
				polygon.setTexturePixelIndices(texturePixelIndices);
				polygon.setTextureWidth(textureWidth);
				polygon.decode();
				int[] vertexIndices = polygon.getShapeVertexIndices();
				// Going backwards, otherwise faces are inside out
				for (int i = vertexIndices.length - 1; i >= 0; i--) {
					Point3f vertex = (Point3f) shapeVertices.get(vertexIndices[i]).clone();
					vertex.add(shapePositionIdsChunk.getShapePosition(shapeId));
					verticesList.add(vertex);
				}
				Point[] relativeUVs = polygon.getRelativeUVs();
				for (int i = relativeUVs.length - 1; i >= 0; i--) {
					int texOriginX = textureStartPixel % textureWidth;
					int texOriginY = textureStartPixel / textureWidth;
					float u = (float) (relativeUVs[i].getX() + texOriginX);
					u /= textureWidth;
					float v = (float) (relativeUVs[i].getY() + texOriginY);
					// UVs have the 0,0 at the bottom left
					v = -v + textureHeight;
					v /= textureHeight;
					texCoordsList.add(new TexCoord2f(u, v));
				}
				stripCountsList.add(polygon.getVerticeCount());
			}
		}
		Point3f[] vertices = verticesList.toArray(new Point3f[verticesList.size()]);
		int[] stripCounts = stripCountsList.stream().mapToInt(i -> i).toArray();
		TexCoord2f[] texCoords = texCoordsList.toArray(new TexCoord2f[texCoordsList.size()]);
		GeometryInfo geometryInfo = new GeometryInfo(GeometryInfo.POLYGON_ARRAY);
		geometryInfo.setCoordinates(vertices);
		geometryInfo.setStripCounts(stripCounts);
		geometryInfo.setTextureCoordinateParams(1, 2);
		geometryInfo.setTextureCoordinates(0, texCoords);
		Appearance appearence = new Appearance();
		TextureLoader textureLoader = new TextureLoader((BufferedImage) textureChunk.getTexture());
		appearence.setTexture(textureLoader.getTexture());
		NormalGenerator normalGenerator = new NormalGenerator();
		normalGenerator.generateNormals(geometryInfo);
		Stripifier stripifier = new Stripifier();
		stripifier.stripify(geometryInfo);
		GeometryArray geometryArray = geometryInfo.getGeometryArray();
		this.shape3d = new Shape3D(geometryArray);
		this.shape3d.setAppearance(appearence);
	}

	@Override
	public AbstractPreviewer getPreviewer(Preview preview) {
		return new Previewer3D(preview, this.shape3d);
	}
}
